package org.hackystat.devcathlon.engine.event.impl;

import javax.xml.datatype.XMLGregorianCalendar;
import org.hackystat.dailyprojectdata.client.DailyProjectDataClient;
import org.hackystat.dailyprojectdata.resource.coverage.jaxb.CoverageDailyProjectData;
import org.hackystat.devcathlon.engine.event.Event;
import org.hackystat.devcathlon.engine.event.EventException;
import org.hackystat.devcathlon.engine.event.EventScore;
import org.hackystat.devcathlon.engine.match.Match;
import org.hackystat.devcathlon.engine.team.Team;
import org.hackystat.sensorbase.resource.projects.jaxb.Project;
import org.hackystat.utilities.logger.HackystatLogger;
import org.hackystat.utilities.tstamp.Tstamp;

/**
 * Awards points to the team with the highest coverage.
 * 
 * <p>
 * Requires the following fields in the configuration.xml file:
 * <ul>
 * <li>WakeupInterval: How frequently this event is invoked, and how far back it looks for data.
 * <li>BaseScoreWeight: An integer multiplied by the base score to get the base points.
 * <li>DayStreak: An integer indicating the number of consecutive days in order to achieve bonus.
 * <li>TypeOfCoverage: A string indicating the kind of coverage to retrieve, such as "Line".
 * <li>StreakBonus: An integer indicating the bonus value, such as 10 points.
 * </ul>
 * </p>
 * <p>
 * Specification:
 * <ul>
 * <li>Wake up once every <i>1</i> day</li>
 * <li>Check coverage for all teams in the match.</li>
 * <li>Award <i>10</i> points to the team with the highest coverage of all.</li>
 * <li>Bonus:
 * <ul>
 * <li>If the same team has had the highest coverage for <i>5</i> days in a row, award them
 * <i>10</i> additional points.</li>
 * </ul>
 * </li>
 * </ul>
 * 
 * All italicized fields can be configured in the configuration.xml file or within the class itself.
 * </p>
 * 
 * @author John Ly
 * 
 */
public class BestCoverage extends Event {

  /** Support serialization. */
  private static final long serialVersionUID = 1L;
  /** Name of the event. */
  public static final String eventName = "Best Coverage";
  /** Description of the event. */
  public static final String description = "Reward the team with the highest coverage in this "
      + "match at the current time.";
  /** Amount of days of high coverage for bonus. */
  private int streakThreshhold;
  /** Amount of points to award for the event. */
  private int baseScore = 10;
  /** Amount of points to award for bonus. */
  private int streakBonusValue;
  /** The property key for day streak. */
  public static final String dayStreakKey = "DayStreak";
  /** The property key for streak bonus value. */
  public static final String streakBonusKey = "StreakBonus";
  /** The property key for the type of coverage. */
  public static final String typeOfCoverageKey = "TypeOfCoverage";
  /** The actual type of coverage. */
  private String typeOfCoverage;
  /** The name of the team that earn this event last. */
  private String lastTeam = "";
  /** The highest percent coverage at the moment. */
  private int highestCoverage;
  /** The streak for lastTeam. */
  private int streak;

  /**
   * Creates a new event for Best Coverage. Ensures all required properties are available.
   * 
   * @throws EventException If the configuration cannot be created.
   */
  public BestCoverage() throws EventException {
    super(eventName, description);
    try {
      streakThreshhold = Integer.parseInt(configManager.getProperty(eventName, dayStreakKey));
      streakBonusValue = Integer.parseInt(configManager.getProperty(eventName, streakBonusKey));
      typeOfCoverage = configManager.getProperty(eventName, typeOfCoverageKey);
      streak = 0;
    }
    catch (Exception e) {
      throw new EventException("Error configuring properties", e);
    }
  }

  /**
   * Returns the total number of points for this event. The total is the base score times the base
   * score weight plus the bonus score times the bonus score weight.
   * 
   * @param score The event score.
   * @return The total points for this event given the current weights.
   */
  @Override
  public int computePoints(EventScore score) {
    return ((score.getBaseScore() * this.baseScoreWeight) + (score.getBonusScore(streakBonusKey) 
        * this.streakBonusValue));
  }

  /**
   * Creates an EventScore instance for BestCoverage.
   * <p>
   * Teams are awarded the base score value of points when:
   * <ul>
   * <li>The team has the highest coverage out of all teams in match.</li>
   * <li>The team tied for the highest coverage with other team(s).</li>
   * </ul>
   * </p>
   * <p>
   * Teams are also awarded a bonus (specified by streakBonusValue) when:
   * <ul>
   * <li>The same team has managed to keep the highest coverage for <i>n</i> days (specified by
   * streakThreshhold).</li>
   * </ul>
   * </p>
   * 
   * @param match The match of interest.
   * @param team The team of interest.
   * @param timestamp the time at which this event is being run.
   * @return The EventScore.
   * @throws EventException If error occurs retrieving data.
   */
  @Override
  public EventScore computeScore(Match match, Team team, XMLGregorianCalendar timestamp)
      throws EventException {

    boolean isHighest = hasHighestCoverage(match, team, timestamp);
    EventScore score = new EventScore(timestamp, match, team);
    if (isHighest) {
      if (this.lastTeam.equals(team.getName())) {
        streak++;
      }
      else {
        this.lastTeam = team.getName();
        streak = 1;
      }
      score.setExplanation(String.format("Team had the highest coverage for the day (%d/%d)",
          timestamp.getMonth(), timestamp.getDay()));
      score.setBaseScore(this.baseScore);
      score.addBonusScore(streakBonusKey, computeBonusScore(score, streak));
    }
    else {
      score = new EventScore(timestamp, match, team);
      score.setBaseScore(0);
      score.setExplanation(String.format("Team did not have highest coverage"));
    }
    return score;

  }

  /**
   * Checks if team specified contains the highest coverage in match.
   * 
   * If team given is the team with highest coverage, returns true. Based on KeepTheCoverageHigh.
   * 
   * @param match The match with all the teams involved.
   * @param team The team we examine if it has the highest coverage.
   * @param timestamp The timestamp when the event is being processed.
   * @return Boolean if team has the highest coverage
   * @throws EventException If error while retrieving the information.
   */
  public boolean hasHighestCoverage(Match match, Team team, XMLGregorianCalendar timestamp)
      throws EventException {

    boolean hasHighestCoverage = false;

    Project project;
    XMLGregorianCalendar startTime = Tstamp.incrementDays(timestamp, -1);

    String projectName = "";
    String owner = "";
    for (Team currentTeam : match.getTeams()) {
      project = match.getProject(currentTeam);
      projectName = project.getName();
      owner = project.getOwner();

      DailyProjectDataClient dpdClient = match.getDailyProjectDataClient(currentTeam);
      HackystatLogger.getLogger("org.hackystat.devcathlon", "devcathlon").info(
          String.format("Getting DPD coverage data for match %s and team %s", match.getName(),
              currentTeam.getName()));
      CoverageDailyProjectData coverageDpd;
      try {
        coverageDpd = dpdClient.getCoverage(owner, projectName, startTime, typeOfCoverage);
      }
      catch (Exception e) {
        throw new EventException(String.format("Error getting coverage for match %s and team %s",
            match.getName(), currentTeam.getName()), e);
      }

      // Check if coverage is higher than the previous
      if (coverageDpd.hasCoverageData()) {
        int currCoverage = coverageDpd.getPercentageCoverage();
        HackystatLogger.getLogger("org.hackystat.devcathlon", "devcathlon").info(
            typeOfCoverage + " coverage is: " + currCoverage);
        if (currCoverage == highestCoverage) {
          if (currentTeam.equals(team)) {
            hasHighestCoverage = true;
          }
        }
        else if (currCoverage > highestCoverage) {
          highestCoverage = currCoverage;
          if (currentTeam.equals(team)) {
            hasHighestCoverage = true;
          }
          else {
            hasHighestCoverage = false;
          }
        }
      }
    }
    return hasHighestCoverage;
  }

  /**
   * Calculates and returns the bonus score.
   * 
   * @param score The EventScore.
   * @param streakCount The streak count for the current team.
   * @return The Bonus Score (1 if rewarded, 0 otherwise).
   */
  private int computeBonusScore(EventScore score, int streakCount) {
    if (streakCount >= streakThreshhold) {
      score.appendExplanation("Highest coverage for " + streakThreshhold + "+ days in a row);");
      return 1;
    }
    return 0;
  }

}
